using System;
using System.Collections.Generic;
using System.Threading;

namespace SmartClientPlatform.RemoteClientBase
{
    internal class Cache<TKey, TValue>
    {
        public void Find<TSlot>(TKey key, IFindHandler<TValue, TSlot> findHandler,
            Func<TValue, TSlot> slotFunc) where TSlot : class
        {
            Find(key, findHandler, slotFunc, value => slotFunc(value) == null);
        }

        public void Find<TSlot>(TKey key, IFindHandler<TValue, TSlot> findHandler,
            Func<TValue, TSlot> slotFunc, Func<TValue, bool> slotIsEmptyFunc)
        {
            Action findedAction = null;
            readerWriterLockSlim.EnterReadLock();
            try
            {
                TValue value;
                if (dictionary.TryGetValue(key, out value))
                {
                    if (!slotIsEmptyFunc(value))
                    {
                        findedAction = () =>  findHandler.OnSlotFind(slotFunc(value));
                    }
                }
            }
            finally
            {
                readerWriterLockSlim.ExitReadLock();
            }

            if (findedAction != null)
            {
                findedAction();
            }
            else
            {
                readerWriterLockSlim.EnterUpgradeableReadLock();
                Action endAction;
                try
                {
                    TValue value;
                    if (dictionary.TryGetValue(key, out value))
                    {
                        if (slotIsEmptyFunc(value))
                        {
                            endAction = findHandler.OnFindEmptySlot(new WriteSlotLocker(this, value));
                        }
                        else
                        {
                            endAction = () => findHandler.OnSlotFind(slotFunc(value));
                        }
                    }
                    else
                    {
                        endAction = findHandler.OnNotFind(new WriteValueLocker(this, key));
                    }
                }
                finally
                {
                    readerWriterLockSlim.ExitUpgradeableReadLock();
                }
                endAction();
            }
        }

        private class WriteSlotLocker : ISlotWriter<TValue>
        {
            private readonly Cache<TKey, TValue> parent;
            private readonly TValue value;

            public WriteSlotLocker(Cache<TKey, TValue> parent, TValue value)
            {
                this.parent = parent;
                this.value = value;
            }

            public void Write(Action<TValue> action)
            {
                parent.WriteLock(() => action(value));
            }
        }

        private void WriteLock(Action action)
        {
            readerWriterLockSlim.EnterWriteLock();
            try
            {
                action();
            }
            finally
            {
                readerWriterLockSlim.ExitWriteLock();
            }
        }

        private readonly Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
        private readonly ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim();

        private class WriteValueLocker : IAdder<TValue>
        {
            private readonly Cache<TKey, TValue> parent;
            private readonly TKey key;

            public WriteValueLocker(Cache<TKey, TValue> parent, TKey key)
            {
                this.parent = parent;
                this.key = key;
            }

            public void Add(TValue value)
            {
                parent.WriteLock(() => parent.dictionary.Add(key, value));
            }
        }
    }

    internal interface IFindHandler<TValue, TSlot>
    {
        void OnSlotFind(TSlot slot);

        Action OnFindEmptySlot(ISlotWriter<TValue> slotWriter);

        Action OnNotFind(IAdder<TValue> adder);
    }

    internal interface IAdder<TValue>
    {
        void Add(TValue value);
    }

    internal interface ISlotWriter<TValue>
    {
        void Write(Action<TValue> action);
    }
}